#include <string.h>
#include <errno.h>
#include <jni.h>
#include "../../ge_internal.h"
#include <android/native_window_jni.h>
#include <android/asset_manager_jni.h>
#include <android_native_app_glue.h>
#include <android/log.h>

#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "GE_JNI", __VA_ARGS__))

void android_main(struct android_app* state);

JavaVM* vm;
struct android_app state;
static bool initialized = false;

int main(int argc, char** argv){
	LOGW("main\n");
	return 0;
}

int8_t android_app_read_cmd(struct android_app* app) {
	int8_t cmd;
	if(read(app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)){
		return cmd;
	}else{
		LOGW("No data on command pipe!");
	}
	return -1;
}

void process_cmd(struct android_app* app, struct android_poll_source* source) {
	LOGW("process_cmd\n");
	int8_t cmd = android_app_read_cmd(app);
	switch(cmd){
        case APP_CMD_INPUT_CHANGED:
            LOGW("APP_CMD_INPUT_CHANGED\n");
            if (app->inputQueue != NULL) {
                AInputQueue_detachLooper(app->inputQueue);
            }
            app->inputQueue = app->pendingInputQueue;
            if (app->inputQueue != NULL) {
                LOGW("Attaching input queue to looper");
                AInputQueue_attachLooper(app->inputQueue, app->looper, LOOPER_ID_INPUT, NULL, &app->inputPollSource);
            }
            break;
        case APP_CMD_INIT_WINDOW:
            LOGW("APP_CMD_INIT_WINDOW\n");
            app->window = app->pendingWindow;
            break;

        case APP_CMD_TERM_WINDOW:
            LOGW("APP_CMD_TERM_WINDOW\n");
            app->window = NULL;
            break;
	}
	if(app->onAppCmd != NULL){
		app->onAppCmd(app, cmd);
	}
}

int cmd_callback(int fd, int events, void* data){
	LOGW("cmd_callback\n");
	return 0;
}

static void process_input(struct android_app* app, struct android_poll_source* source) {
	AInputEvent* event = NULL;
	if (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
		LOGW("New input event: type=%d\n", AInputEvent_getType(event));
		if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
			return;
		}
		int32_t handled = 0;
		if (app->onInputEvent != NULL){
			handled = app->onInputEvent(app, event);
		}
		AInputQueue_finishEvent(app->inputQueue, event, handled);
	} else {
		LOGW("Failure reading next input event: %s\n", strerror(errno));
	}
}

jint JNI_OnLoad(JavaVM *_vm, void *reserved){
	memset(&state, 0, sizeof(struct android_app));

	vm = _vm;

	LOGW("JNI_OnLoad OK !\n");
	return JNI_VERSION_1_2;
}




static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
	if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
		LOGW("Failure writing android_app cmd: %s\n", strerror(errno));
	}
}

static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) {
	android_app->pendingInputQueue = inputQueue;
	android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED);
}

static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) {
    if (android_app->pendingWindow != NULL) {
        android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
    }
    android_app->pendingWindow = window;
    if (window != NULL) {
        android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
    }
}

static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) {
    LOGW("NativeWindowCreated: %p -- %p\n", activity, window);
    android_app_set_window((struct android_app*)activity->instance, window);
}

static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
    LOGW("NativeWindowDestroyed: %p -- %p\n", activity, window);
    android_app_set_window((struct android_app*)activity->instance, NULL);
}

static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
	LOGW("InputQueueCreated: %p -- %p\n", activity, queue);
	android_app_set_input((struct android_app*)activity->instance, queue);
}

static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
	LOGW("InputQueueDestroyed: %p -- %p\n", activity, queue);
	android_app_set_input((struct android_app*)activity->instance, NULL);
}

void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize){
	LOGW("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH");
	activity->callbacks->onInputQueueCreated = onInputQueueCreated;
	activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
	activity->instance = &state;
	state.activity = activity;
}



ge_File* geJniOpenFd(JNIEnv* env, jobject jfd, jint ofs, jint len){
	jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
	jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
	jint fd = (*env)->GetIntField(env, jfd, fdClassDescriptorFieldID);

	ge_File* file = geFileOpenFd(fd, GE_FILE_MODE_BINARY | GE_FILE_MODE_READ);
	if(file){
		ge_file_desc* desc = (ge_file_desc*)file->desc;
		desc->fake_offset = ofs;
		desc->size = len;
		geFileRewind(file);
	}
	return file;
}

void Java_com_ssp_libge_GEActivity_ngeInit(JNIEnv* env, jobject this, jobject assetManager, jstring internalDataPath, jstring externalDataPath){
	LOGW("geInit 1 \n");
	const char* pinternalDataPath  = (*env)->GetStringUTFChars(env, internalDataPath, NULL);
	const char* pexternalDataPath = (*env)->GetStringUTFChars(env, externalDataPath, NULL);
	LOGW("geInit 2 \n");
	LOGW("geInit 3 \n");
	LOGW("geInit 4 (0x%08X)\n", state.activity);
	LOGW(" => (0x%08X)\n", state.activity->vm);
	LOGW("geInit 4.1 \n");
	LOGW("geInit 5 \n");
	state.activity->internalDataPath = strdup((char*)pinternalDataPath);
	state.activity->externalDataPath = strdup((char*)pexternalDataPath);
	LOGW("geInit 6 \n");
	state.activity->assetManager = AAssetManager_fromJava(env, assetManager);
	LOGW("geInit 7 \n");
	if(pinternalDataPath)(*env)->ReleaseStringUTFChars(env, internalDataPath, pinternalDataPath);
	if(pexternalDataPath)(*env)->ReleaseStringUTFChars(env, externalDataPath, pexternalDataPath);
	LOGW("geInit 8 \n");

	geDebugMode((GE_DEBUG_INFO|GE_DEBUG_WARN|GE_DEBUG_ERROR|GE_DEBUG_PRINT));
	LOGW("geInit 9 \n");
	geInit();
	LOGW("geInit 10 \n");
	geDebugMode((GE_DEBUG_INFO|GE_DEBUG_WARN|GE_DEBUG_ERROR|GE_DEBUG_PRINT));
	LOGW("geInit 11 \n");
	
	int msgpipe[2];
	if (pipe(msgpipe)) {
		LOGW("could not create pipe: %s", strerror(errno));
	}
	state.msgread = msgpipe[0];
	state.msgwrite = msgpipe[1];
	LOGW("Pipe : %d %d\n", msgpipe[0], msgpipe[1]);
	state.cmdPollSource.id = LOOPER_ID_MAIN;
	state.cmdPollSource.app = &state;
	state.cmdPollSource.process = process_cmd;
	state.inputPollSource.id = LOOPER_ID_INPUT;
	state.inputPollSource.app = &state;
	state.inputPollSource.process = process_input;
	/*
	ALooper* looper = ALooper_forThread();
	ALooper_addFd(looper, state.msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, &cmd_callback, &state.cmdPollSource);
	state.looper = looper;
	*/
}

void Java_com_ssp_libge_GESurfaceView_geJavaInit(JNIEnv* env, jobject this, jobject surface){
	geDebugMode((GE_DEBUG_INFO|GE_DEBUG_WARN|GE_DEBUG_ERROR|GE_DEBUG_PRINT));

	LOGW("geJavaInit 1 \n");
	state.window = ANativeWindow_fromSurface(env, surface);
	LOGW("geJavaInit 2 (0x%08X)\n", state.window);
	ANativeWindow_acquire(state.window);
	onNativeWindowCreated(state.activity, state.window);

	if(!initialized){
		LOGW("geJavaInit 3 \n");
		android_main(&state);
		LOGW("geJavaInit 4 \n");
		initialized = true;
	}
		
	LOGW("geJavaInit 5 \n");
}

void Java_com_ssp_libge_GESurfaceView_geSurfaceDestroy(JNIEnv* env, jobject this){
	LOGW("geSurfaceDestroy 1 \n");
	onNativeWindowDestroyed(state.activity, state.window);
	LOGW("geSurfaceDestroy 2 \n");
}

ge_Font* fnt = NULL;
void Java_com_ssp_libge_ge_CreateMainWindow(JNIEnv* env, jobject this, jstring title, jint width, jint height, jint flags){
	LOGW("geCreateMainWindow\n");

	ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
	ALooper_addFd(looper, state.msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, &state.cmdPollSource);
	state.looper = looper;
	ALooper_acquire(looper);
	ALooper_wake(looper);
	LOGW("LOOPER: 0x%08X\n", (u32)looper);

	const char* ptitle = (*env)->GetStringUTFChars(env, title, NULL);
	geCreateMainWindow((char*)ptitle, width, height, flags);
	if(ptitle){
		(*env)->ReleaseStringUTFChars(env, title, ptitle);
	}
	geDebugMode((GE_DEBUG_INFO|GE_DEBUG_WARN|GE_DEBUG_ERROR|GE_DEBUG_PRINT));

	fnt = geLoadFont("");
	geFontSize(fnt, 16);
	geWaitVsync(false);
}

void Java_com_ssp_libge_ge_ClearColor(JNIEnv* env, jobject this, jint color){
	geClearColor(color);
}

void Java_com_ssp_libge_ge_ClearScreen(JNIEnv* env, jobject this){
	geClearScreen();
}

void Java_com_ssp_libge_ge_SwapBuffers(JNIEnv* env, jobject this){
	geFontPrintfScreen(0, 0, fnt, 0xFFFFFFFF, "FPS: %d\nRAM: %d\nVRAM: %d", geFps(), geGetContext()->mem/1024, geGetContext()->gpumem/1024);
	geSwapBuffers();
}